import { Color, CubeCamera3D, FogExp23D, MeshBasicMaterial3D, Node3D, PerspectiveCamera3D, Scene3D, StatsComponent, Vector3, WebGLCubeRenderTarget3D, WebGLRenderer3D } from 'feng3d-three';
import GUI from 'lil-gui';
import { Bone, Mesh, SkinnedMesh } from 'three';
import { CCDIKSolver3D } from '../../extension/animation/CCDIKSolver3D';
import '../../extension/controls/OrbitControls3D';
import { OrbitControls3D } from '../../extension/controls/OrbitControls3D';
import '../../extension/controls/TransformControls3D';
import { TransformControls3D } from '../../extension/controls/TransformControls3D';
import { DRACOLoader3D } from '../../extension/loaders/DRACOLoader3D';
import { GLTFLoader3D } from '../../extension/loaders/GLTFLoader3D';

const decoderPath = '../../libs/draco/';
const gltfUrl = '../../models/gltf/kira.glb';

let scene: Scene3D;
let camera: PerspectiveCamera3D;
let renderer: WebGLRenderer3D;
let orbitControls: OrbitControls3D;
let transformControls: TransformControls3D;
let mirrorSphereCamera: CubeCamera3D;

const OOI: {
    head: Bone
    lowerarm_l: Bone
    Upperarm_l: Bone
    hand_l: Bone
    target_hand_l: Bone
    sphere: Mesh
    kira: SkinnedMesh
} = {} as any;
let IKSolver: CCDIKSolver3D;

let stats: StatsComponent;
let gui: GUI;
const conf = {
    followSphere: false,
    turnHead: true,
    ik_solver: true,
};
const v0 = new Vector3();

init().then(animate);

async function init()
{
    scene = new Node3D().addComponent('Scene3D');

    scene.fog = new FogExp23D(0xffffff, 0.17);
    scene.background = new Color(0xdddddd);

    camera = new Node3D().addComponent('PerspectiveCamera3D', { fov: 55, aspect: window.innerWidth / window.innerHeight, near: 0.001, far: 5000 });
    camera.entity.position.set(0.9728517749133652, 1.1044765132727201, 0.7316689528482836);
    camera.entity.lookAt(scene.entity.position);

    scene.addComponent('AmbientLight3D', { color: { value: 0xffffff }, intensity: 8 }); // soft white light

    renderer = scene.addComponent('WebGLRenderer3D', { parameters: { antialias: true, logarithmicDepthBuffer: true } });
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.outputEncoding = 'sRGBEncoding';
    renderer.physicallyCorrectLights = true;
    document.body.appendChild(renderer.domElement);

    stats = scene.addComponent('StatsComponent');
    document.body.appendChild(stats.dom);

    orbitControls = new Node3D().addComponent('OrbitControls3D', { camera, domElement: renderer.domElement });
    orbitControls.minDistance = 0.2;
    orbitControls.maxDistance = 1.5;
    orbitControls.enableDamping = true;

    const dracoLoader = new DRACOLoader3D();
    dracoLoader.setDecoderPath(decoderPath);
    const gltfLoader = new GLTFLoader3D();
    gltfLoader.setDRACOLoader(dracoLoader);

    const gltf = await gltfLoader.loadAsync(gltfUrl);
    gltf.scene._group.traverse((n: any) =>
    {
        if (n.name === 'head') OOI.head = n;
        if (n.name === 'lowerarm_l') OOI.lowerarm_l = n;
        if (n.name === 'Upperarm_l') OOI.Upperarm_l = n;
        if (n.name === 'hand_l') OOI.hand_l = n;
        if (n.name === 'target_hand_l') OOI.target_hand_l = n;

        if (n.name === 'boule') OOI.sphere = n;
        if (n.name === 'Kira_Shirt_left') OOI.kira = n;

        if (n.isMesh) n.frustumCulled = false;
    });
    scene.entity.addChild(gltf.scene);

    orbitControls.target.copy(OOI.sphere.position); // orbit controls lookAt the sphere
    OOI.hand_l.attach(OOI.sphere);

    // mirror sphere cube-camera
    const cubeRenderTarget = new WebGLCubeRenderTarget3D(1024);
    mirrorSphereCamera = new Node3D().addComponent('CubeCamera3D', { near: 0.05, far: 50, renderTarget: cubeRenderTarget });
    scene.entity.addChild(mirrorSphereCamera.entity);
    const mirrorSphereMaterial = new MeshBasicMaterial3D({ envMap: cubeRenderTarget.texture });
    OOI.sphere.material = mirrorSphereMaterial._material;

    transformControls = new Node3D().addComponent('TransformControls3D', { camera, domElement: renderer.domElement });
    transformControls.size = 0.75;
    transformControls.showX = false;
    transformControls.space = 'world';
    transformControls.attach(OOI.target_hand_l);
    scene.entity.addChild(transformControls.entity);
    // disable orbitControls while using transformControls
    transformControls.emitter.on('mouseDown', () => orbitControls.enabled = false);
    transformControls.emitter.on('mouseUp', () => orbitControls.enabled = true);

    OOI.kira.add(OOI.kira.skeleton.bones[0]);
    const iks: any = [
        {
            target: 22, // "target_hand_l"
            effector: 6, // "hand_l"
            links: [
                {
                    index: 5, // "lowerarm_l"
                    rotationMin: new Vector3(1.2, -1.8, -0.4),
                    rotationMax: new Vector3(1.7, -1.1, 0.3)
                },
                {
                    index: 4, // "Upperarm_l"
                    rotationMin: new Vector3(0.1, -0.7, -1.8),
                    rotationMax: new Vector3(1.1, 0, -1.4)
                },
            ],
        }
    ];
    IKSolver = new CCDIKSolver3D(OOI.kira, iks);
    const ccdikhelper = new Node3D().addComponent('CCDIKHelper3D', { mesh: OOI.kira, iks, sphereSize: 0.01 });
    scene.entity.addChild(ccdikhelper.entity);

    gui = new GUI();
    gui.add(conf, 'followSphere').name('follow sphere');
    gui.add(conf, 'turnHead').name('turn head');
    gui.add(conf, 'ik_solver').name('IK auto update');
    gui.add(IKSolver, 'update').name('IK manual update()');
    gui.open();

    window.addEventListener('resize', onWindowResize, false);
}

function animate()
{
    if (OOI.sphere && mirrorSphereCamera)
    {
        OOI.sphere.visible = false;
        OOI.sphere.getWorldPosition(mirrorSphereCamera.entity.position);
        mirrorSphereCamera.update(renderer, scene);
        OOI.sphere.visible = true;
    }

    if (OOI.sphere && conf.followSphere)
    {
        // orbitControls follows the sphere
        OOI.sphere.getWorldPosition(v0);
        orbitControls.target.lerp(v0, 0.1);
    }

    if (OOI.head && OOI.sphere && conf.turnHead)
    {
        // turn head
        OOI.sphere.getWorldPosition(v0);
        OOI.head.lookAt(v0);
        OOI.head.rotation.set(OOI.head.rotation.x, OOI.head.rotation.y + Math.PI, OOI.head.rotation.z);
    }

    if (conf.ik_solver)
    {
        if (IKSolver) IKSolver.update();
    }

    orbitControls.update();
    renderer.render(scene, camera);

    stats.update(); // fps stats

    requestAnimationFrame(animate);
}

function onWindowResize()
{
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}
